Raspberry PI | Bluetooth | Mopidy

**Начало**

У меня есть DLNA\UPnP сервер с моей аудио коллекцией.  Есть несколько Интернет радиостанций, которые я периодически слушаю. Хотелось управлять прослушиванием музыки с помощью телефона или web-браузера и при этом перемещаться по дому с телефоном и не слышать кваканье в колонках, когда отходишь достаточно далеко от них, как это бывает при воспроизведении через Bluetooth. Правда при прослушивании стриминговой музыки с Интернета могут быть затупы и пропадания звука, но это связано с особенностями буферизации потока и с плохим каналом в Интернет, а не с растоянием от телефона до звуковой колонки.

Я решил реализовать своё желание с помощью старого RPI Model B, старого Bluetooth  USB стика, старых колонок и китайского "усилителя" с функцией Bluetooth. 

Состав проекта:
- Raspberry Pi Model B Rev 2
- HIFI беспроводной Bluetooth 5,0 TPA3116 (китайский усилитель)
- USB Bluetooth dongle
- Колонки от какого-то музыкального центра Aiwa

RPI Model B - достаточно старая и маломощная железяка, не умеющая грузиться с usb устройств, не имеющая встроенного WiFi или Bluetooth и не годная для большинства современных проектов под RPI. Но она подходит для того, чтобы быть MPD сервером и DLNA-render'ом. Она у меня была уже много лет и последнее время лежала без дела.

Китайский усилитель - это коробочка размером 5x10 см с ручкой увеличения громкости, Bluetooth-ом и входом Mini-Jack 3.5. Также на нем есть винтовые разъемы для подключения колонок и вход для питания от 9 до 24 Вольт.  Блок питания для него я взял от какого-то старого неработающего ноутбука. За все это на Али просили 1500 рублей на момент написания статьи. По названию оно легко гуглится.

USB Bluetooth стик у меня валялся без дела, но пока я пытался выкроить для всего этого время, он куда то потерялся и пришлось искать новый. В магазинах шаговой доступности за эту ерунду просили больше 800 рублей, поэтому в который раз выручил Али. Там я купил это в 3 раза дешевле и доставка была в течении 2-х дней. 

Колонки мне достались бесплатно. Кто-то собрался выкинуть старый музыкальный центр с CD чейнжером на 5 дисков. Я забрал колонки, а остальное отнес в мусор.

Я захотел собрать все это в кучу, чтобы получить боле-менее приличный звук, минимум проводов и удобное управление.

В предыдущей версии проекта соединение RPI и HIFI усилителя было реализовано через проводное подключение MiniJack 3.5 <=> MiniJack 3.5. В этой версии я захотел избавиться от этого проводного подключения и разнести в пространстве RPI и колонки с усилителем. Причины:
- колонки уже были установлены в доме на своем месте, а так как у моей версии RPI нет WiFi, то не хотелось тащить туда ethernet, так же как и разводить там кучу тройников, чтобы одновременно запитать усилитель и RPI
- не было желания кидать провод от RPI до усилителя

В качестве MPD сервера выбрал [Mopidy](https://mopidy.com/) - музыкальный сервер на python.  

Установка Mopidy под RPI описана по [ссылке](https://docs.mopidy.com/en/latest/installation/raspberrypi/). Ниже я буду выкладывать свои действия, основанные на той статье и своем неоднократном опыте развертывания  этого проекта. Т.е. я неоднократно разворачивал  систему с нулевого до финального состояния. Зачем? Потому что у меня не получалось добиться положительного результата по своим собственным записям. 

У меня была рабочая SD карта от первой версии проекта с которой усилитель подключался по проводу и новая SD карта с которой я тренировался и пытался настроить автоподключение RPI к Bluetooth усилителю. Пока я разворачивал все в первый раз, я стал набрасывать перечень своих действий и ссылок в файл. Когда в первый раз все получилось, я решил потренироваться на рабочей SD карте по своим наброскам. И ничего не получилось. Я понял, что важен порядок в выполнении действий, что чего-то не хватает в записях, что-то напутал. Тогда я исправил все в очередной раз. Внес дополнительные комментарии, чтобы в последствии не мучиться вопросом - зачем тут сделано что-то так, а не иначе. Поменял карты местами, начал разворачивать снова и столкнулся с тем, что опять чего-то не хватало, а что-то было избыточным. Следующая итерация по исправлению своих записей. В очередной раз развернул все с нуля и получил желаемый результат. 

Все сложности с которыми я столкнулся во время развертывания этого пет-проекта сподвигли меня на то, чтобы обобщить свой опыт и оформить в статью. Дабы в будущем обращаться к ней и понимать, почему выбран тот или иной способ решения проблемы. Другой причиной оформить все в какую-то 100% работающую инструкцию было то, что жизненный цикл SD карточек недолог и они могут умирать внезапно и тогда мне придется проделывать все это еще раз.

**Мой велосипед**

Можно не мучить себе мозги и воспользоваться официальной тулзой для развертывания raspbian - `rpi-imager`. Она позволяет выбирать куда писать образ системы, затем скачивает его, записывает, преднастраивает на запуск ssh, а также задает пароль пользователя. В случае использования этой утилиты первые четыре пункта можно пропустить и перейти сразу к пятому. Но это не мой путь. 

1. качаем [Raspberry Pi OS Lite](https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2022-04-07/2022-04-04-raspios-bullseye-armhf-lite.img.xz)

2. определяем SD карту, на которую будем разворачивать образ. Тут главное не ошибиться, а то можно все сломать. 

3. после завершения скачивания надо залить скачанный образ на SD карту: `xz -dc 2022-04-04-raspios-bullseye-armhf-lite.img.xz | sudo dd of=/dev/<SDCARD> bs=4M`

4. после того как образ разлился не торопимся вытаскивать SD и втыкать ее в RPI. Дело в том, что по умолчанию RPI загружается без ssh и с пустым паролем пользователя`pi`, так что зайти на нее потом без клавиатуры и монитора не получится. [Подробности.](https://www.raspberrypi.com/news/raspberry-pi-bullseye-update-april-2022/) Исправляем:

 Перечитываем партиции после dd:
 
`sudo partprobe`

Создаем необходимую директорию:

`sudo mkdir -p /mnt/sdcard/part1`

Монтируем boot партицию:

`sudo mount /dev/<SDCARD>1 /mnt/sdcard/part1`

Включаем ssh сервер на RPI:

`sudo touch /mnt/sdcard/part1/ssh`

Устанавливаем <ПАРОЛЬ> для пользователя pi
```
PASSWORD=`echo '<ПАРОЛЬ>' | openssl passwd -6 -stdin` && echo pi:$PASSWORD | sudo tee /mnt/sdcard/part1/userconf
```

Размонтируем SD карту:

`sudo umount /mnt/sdcard/part1`

5. Вставляем SD карту в RPI и загружаемся. Первая загрузка займет какое-то время.

6. Логинимся в RPI по ssh. Адрес можно узнать на вашем домашнем роутере в DHCP leases. Вообще-то можно и не по ssh, а с помощью клавиатуры и монитора, но я изначально хотел настраивать все в headless  режиме.

Демон mopidy есть в репозиториях debian, но он там немного старее. Поэтому я добавлял репозиторий apt.mopidy.com:

7. Добавляем GPG ключ репы apt.mopidy.com:

`sudo mkdir -p /usr/local/share/keyrings`

`sudo wget -q -O /usr/local/share/keyrings/mopidy-archive-keyring.gpg https://apt.mopidy.com/mopidy.gpg`

8. Добавляем нужный репозиторий:

`sudo wget -q -O /etc/apt/sources.list.d/mopidy.list https://apt.mopidy.com/buster.list`

9. Установка и настройка всего остального

Геренируем необходимые локали:

```
echo 'en_GB.UTF-8 UTF-8
en_US.UTF-8 UTF-8
ru_RU.UTF-8 UTF-8' | sudo tee /etc/locale.gen
sudo locale-gen
```

Обновляем список пакетов, обновляемся и перезагружаемся:

`sudo apt update && sudo apt -y upgrade && sudo reboot`

Устанавливаем сам сервис `mopidy`:

`sudo apt install mopidy` 

Устанавливает модуль Bluetooth для PulseAudio, зависимостями подтягивается все нужное:

`sudo apt install pulseaudio-module-bluetooth` 

Устанавливаем так называемые расширения mopidy. Их [много](https://mopidy.com/ext/). Устанавливать можно как из системного репозитория, так и через pip3. Для моих целей этого хватает:

`sudo apt install mopidy-dleyna mopidy-mpd` 

Для установки web клиентов, которых нет в APT репозитории:

`sudo apt install python3-pip`

Красивый web клиент:

`sudo pip3 install Mopidy-Iris` 

Web клиент которым я всегда пользуюсь:

`sudo pip3 install Mopidy-MusicBox-Webclient` 

Иначе не воспроизводились некоторые mpeg файлы

`sudo apt install gstreamer1.0-plugins-bad -y` 

После того как поставили все нужное и прежде чем продолжить, необходимо добавить пользователей в нужные группы:

```
sudo gpasswd -a pi bluetooth
sudo gpasswd -a pi pulse
sudo gpasswd -a pi pulse-access  # чтобы потом была возможность проверить из терминала вывод в pulseaudio в system-wide режиме

sudo gpasswd -a pulse bluetooth
sudo gpasswd -a mopidy pulse-access
sudo gpasswd -a mopidy pulse
sudo gpasswd -a mopidy video

sudo gpasswd -a root pulse-access # root тоже должен быть в этой группе. так написано в доке
```

Установка всего вышеперечисленного и добавление пользователей в необходимые группы это как раз результат неоднократного развертывания проекта по первым версиям инструкции. Без этого не хватало стройности всем действиям и для достижения положительного результата приходилось непоследовательно выполнять инструкции из разных частей статьи. 

Выполняем перезагрузку.

**Bluetooth**

В RPI Model B нет встроенного Bluetooth, поэтому я взял самый простой USB донгл в формате микро. Ссылку на Али давать не буду, ключевые слова: "bluetooth usb adapter baseus".

Включаем Fast Connectable для поддерживающих адаптеров (необязательно):
```
sudo sed -i -re 's/^#FastConnectable = false.*/FastConnectable = true/' /etc/bluetooth/main.conf
```

Переводим Bluetooth контроллер в режим BR/EDR (без этого у меня не работало, но возможно на нормальном Bluetooth адаптере это будет необязательно):
```
sudo sed -i -re 's/^#ControllerMode = dual.*/ControllerMode = bredr/' /etc/bluetooth/main.conf
```

Теперь надо заставить RPI подключаться к колонке при каждой перезагрузке. Иначе приходится каждый раз лезть в консоль и запускать вручную. Штатного механизма для этого я не нашел, хотя и пытался. Все найденные способы для меня оказывались нерабочими. В частности способ с кроном, описанный [тут](https://wiretuts.com/connect-mopidy-to-bluetooth-speaker-on-raspberry-pi) у меня не работал. Может потому что я делал все на LITE образе Raspbian и там не хватало каких-то пакетов, а может по каким-то другим причинам. Я какое-то время помучился в поисках решения автоподключения и в очередной раз решил пойти своим путем. 

К этому моменту в системе должна быть утилита `bluetoothctl` из пакета `bluez`. Если ее нет, значит что-то пошло не так на предыдущих шагах. Для корректной работы сервиса необходимо знать MAC адрес Bluetooth усилителя (Bluetooth колонки). Узнать его можно через `bluetoothctl scan on`. В выводе команды должны появиться доступные устройства, среди которых должен быть нужный. После определения нужного MAC адреса можно прервать выполнение сканирования, скопировать его и вставить его в файл сервиса ниже. 

Автоподключение реализовал посредством старта сервиса systemd `/etc/systemd/system/bt-wuzhi.service`:

```
[Unit]
Description=Connect to Bluetooth speaker BT-WUZHI
Requires = rsyslog.service network.target bluetooth.target
After = rsyslog.service network.target bluetooth.target

[Service]
Type=simple
ExecCondition=sh -c 'bluetoothctl info <MAC> | grep -q "Connected: no"'
ExecStart=bluetoothctl connect <MAC>
RemainAfterExit=yes
ExecStop=bluetoothctl disconnect <MAC>

[Install]
WantedBy=multi-user.target
```

Дальше перечитываем изменения для демонов и включаем сервис при загрузке:
```
sudo systemctl daemon-reload
sudo systemctl enable bt-wuzhi.service
```

Сперва казалось, что на этом все. Mopidy установлен, сервис для автосоединения написан. Но как оказалось все не так просто. Самописный сервис отказывался стартовать при запуске системы, но прекрасно стартовал вручную после коннекта по ssh. Дело было в pulseaudio. 

**PulseAudio**

По-умолчанию, в многопользовательской системе этот демон работает per-user режиме и не работает system-wide. Для этого есть [много](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/WhatIsWrongWithSystemWide/) причин и в [статье](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/SystemWide/) по настройке system-wide режима об этом всячески предупреждают.  Но мне нужен именно system-wide и дальше про то, как его завести.

Выключаем штатное поведение pulseaudio:

`sudo systemctl --global disable pulseaudio.service pulseaudio.socket`

Выключаем механизм autospawn:

`sudo sed -i -re 's/^.+autospawn.+/autospawn = no/' /etc/pulse/client.conf`

Обязательно указываем домашний директорий для пользователя pulse, иначе жалуется и не хочет стартовать в system-wide:
```
sudo usermod -d /var/run/pulse pulse
```

Добавляем настройки для автозагрузки модулей в конец системного файл `/etc/pulse/system.pa`, т.к. поменяли режим:

```
### Automatically load driver modules for Bluetooth hardware
.ifexists module-bluetooth-policy.so
load-module module-bluetooth-policy
.endif

.ifexists module-bluetooth-discover.so
load-module module-bluetooth-discover autodetect_mtu=yes
.endif
```

Ну и по традиции пишем свой файл сервис systemd `/etc/systemd/system/pulseaudio-system-wide.service`:

```
[Unit]
Description=PulseAudio system server

[Service]
Type=notify
ExecStart=pulseaudio --daemonize=no --system --realtime --log-target=journal

[Install]
WantedBy=multi-user.target
```

Дальше перечитываем изменения для демонов и включаем сервис при загрузке:

```
sudo systemctl daemon-reload
sudo systemctl enable pulseaudio-system-wide.service
```

**Bluetooth 2**

Дальше необходимо запейриться с усилителем по Bluetooth. Все приведенные ниже команды выполняются из под пользователя `pi`
 
Проверяем, что RPI видит Bluetooth адаптер:

```
bluetoothctl list
Controller AA:BB:CC:DD:EE:FF hostname [default]
```

Включен ли адаптер можно увидеть в выводе `bluetoothctl show`. Но если лень проверять, то включаем принудительно:

`bluetoothctl power on` 

Включаем режим "видимости" нашего Bluetooth USB донгла:

`bluetoothctl discoverable on` 

После выполнения следующего шага, в выводе комманды, должен появиться HIFI ресивер и мы должны запомнить его MAC. Если выполнять команду из shell консоли, то прервать работу команды можно по `Ctrl+C`, иначе оно будет продолжаться вечно. Если выполнять `scan on` непосредственно из `cli` самой утилиты `bluetoothctl`, то для завершения поиска надо набрать команду `scan off`.  Именно после начала сканирования  начинались у меня могли появляться всякие странности в работе BLuetooth. Но без скана, заранее зная MAC адрес Bluetooth колонки, у меня не получалось выполнить дальнейшие инструкции:

`bluetoothctl scan on`

Тут в выводе должно появиться - `Changing <MAC> trust succeeded`:

`bluetoothctl trust <MAC>` 

Тут должно быть `Pairing successful`:

`bluetoothctl pair <MAC>` 

 Если ошибка, то можно повторить еще раз.

 Тут должно быть `Connection successful`:
 
`bluetoothctl connect <MAC>`

 Если ошибка, то можно повторить еще раз.

Если все ОК, то при попытке воспроизведения файла мы должны услышать звук через нашу импровизированную Bluetooth колонку:
`aplay /usr/share/sounds/alsa/Front_Right.wav`

Как правило, после команды scan или pair начиналась непонятная мне борьба с подключением к колонкам и странным поведением самого Bluetooth адаптера. На любую команду отдавалась ошибка и не помогала перезагрузка. Выявить закономерность я не смог. Я поочередно отключал питание на HIFI ресивере, даже перетыкал Bluetooth USB донгл в другой порт. Но это ни к чему положительному не приводило. Поэтому я удалял MAC адрес усилителя из `devices` и начинал процедуру с начала и повторял до успешного завершения. Как только удавалось присоединиться к Bluetooth ресиверу, то дальше все продолжало работать и не разваливалось. Возможно это особенность именно моих устройств и на "нормальных" Bluetooth девайсах такого не будет.

**Mopidy**

Мой конфиг `/etc/mopidy/mopidy.conf` для сервиса. Коментировать тут особо нечего, он прямой как палка, все понятно из названий опций:

```
[m3u]
default_encoding = ru_RU.utf8

[mpd]
enabled = true
hostname = 0.0.0.0

[http]
enabled = true
hostname = 0.0.0.0

[audio]
output = autoaudiosink
```

Не забываем включить старт сервиса при загрузке:

`sudo systemctl enable mopidy.service`

Mopidy на RPI стартует очень долго и может сложиться впечатление, что ничего не работает. Поэтому могу лишь порекоммендовать смотреть логи и ждать. 

Установленные web-клиенты будут доступны после перезагрузки по порту 6680, протокол http. Т.е. ссылка будет что-то вроде http://mopidy.localdomain:6680/

**UPnP**

Про DLNA\UPnP в разрезе работы с Mopidy написано [тут](https://docs.mopidy.com/en/latest/upnp/)

`upmpdcli` нет в стандартных репозиториях, поэтому их надо добавить.

```
sudo curl https://www.lesbonscomptes.com/pages/lesbonscomptes.gpg -o /usr/share/keyrings/lesbonscomptes.gpg
sudo curl https://www.lesbonscomptes.com/upmpdcli/pages/upmpdcli-rbullseye.list -o /etc/apt/sources.list.d/upmpdcli-rbullseye.list
sudo apt update
sudo apt install upmpdcli
sudo systemctl enable upmpdcli.service
```

Вообще DLNA\UPnP это [круто](https://www.lesbonscomptes.com/upmpdcli/), если работает. Но я не нашел рабочего, свободного от рекламы, UPnP контроллера для телефона. Я пробовал: ControlDLNA, DroidUPnP, PlainUPnP, Yaacc. Проблем не возникло только с BubbleUPnP. Оно умеет играть музыку и с локального хранилища телефона и с медиасерверов в локальной сети. Но проблема в том, что я не могу купить его в Google Play, а бесплатное использование ограничено в функциях. Ну и в целом BubbleUPnP это целый комбайн со своим медиасервером и проксированием через себя подключений. Не нравится. 

С web морды RPI проигрывать музыку с UPnP серверов локальной сети можно без проблем.

**Завершение**

В конце перезагружаемся. При старте системы RPI, если все прошло успешно, должны услышать характерный звук подключения RPI к Bluetooth ресиверу. Так же, по прошествии нескольких минут не должно быть звука отключения от Bluetooth колонки. Если такое происходит надо проверить права пользователей. Ждем пока загрузится mopidy и можно пользоваться. 

С телефона я пользуюсь MPDroid для управления MPD сервисом и RadioDroid для прослушивания радио через MPD сервис. Ну и иногда BubbleUPnP, если надо включить что-то находящееся на телефоне. 

**Итог**

По итогу я добился чего хотел. Могу выбирать радио на телефоне, а проигрывать на MPD сервере. Могу запустить радио с телефона, уйти, уехать, выключить телефон, а запущенное радио будет воспроизводиться пока RPI будет в сети.  Могу проигрывать музыку с домашнего медиасервера на этой "колонке" управляя воспроизведением через браузер или с телефона. При определенных обстоятельствах могу воспроизводить музыку, хранящуюся на телефоне на этой системе. Ну и могу радоваться отсутствию лишних проводов.  

В статье совсем не затронута тема связанная с ограничением доступа к RPI и Mopidy. Но надо понимать, что делал я это для своей домашней сети и никакой авторизации для этих сервисов я не предусматривал бай дизайн. Но это лишь потому, что "злоумышленников" в этой сети быть не должно. Если же делать сервис на публичной сети, то этот момент необходимо учитывать. 

Есть еще один момент касающийся именно моей версии RPI. Она не умеет грузиться с mass-storage устройств, подключенных к USB портам. Поэтому в моем случае система находится на SD карте. Следовательно любые файловые действия в системе приводят к тормозам и если в этот момент играет музыка, то она будет прерываться. Вывод - не надо использовать эту систему ни для чего, что потребует интенсивного взаимодействия с "диском" и по-возможности отключать неиспользуемые сервисы. Можно конечно оставлять загрузчик на SD карте, а систему размещать на USB-HDD, но во-первых моя версия RPI [не указана](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#booting-from-usb-mass-storage) в перечне поддерживаемых устройств, во-вторых это дорого, а в-третьих эта RPI слишком ограничена в ресурсах.  